package com.google.code.stalkernet.entry;


/**
 * Contains various useful methods to work with entries.
 *
 * @author Mikhail Vladimirov
 */
public class EntryUtils
{
    private EntryUtils ()
    {
        throw new Error ("Do not instantiate me");
    }

    /**
     * Create entry ID from given sequence of bytes.
     *
     * @param bytes entry ID as a sequence of bytes
     * @return {@link EntryID} object
     */
    public static EntryID createEntryID (byte [] bytes)
    {
        if (bytes == null)
            throw new IllegalArgumentException ("Bytes is null");

        return new MyEntryID (bytes);
    }

    /**
     * Create entry body from given sequence of bytes.
     *
     * @param bytes entry body as a sequence of bytes
     * @return {@link EntryBody} object
     */
    public static EntryBody createEntryBody (byte [] bytes)
    {
        if (bytes == null)
            throw new IllegalArgumentException ("Bytes is null");

        return new MyEntryBody (bytes);
    }

    /**
     * Create new {@link Entry} with given entry ID, entry body and parent IDs.
     *
     * @param entryID ID of the new entry
     * @param entryBody body of the new entry
     * @param parentIDs parent IDs of the new entry
     * @return new {@link Entry}
     */
    public static Entry createEntry (
        EntryID entryID, EntryBody entryBody, EntryID [] parentIDs)
    {
        if (entryID == null)
            throw new IllegalArgumentException ("Entry ID is null");

        if (entryBody == null)
            throw new IllegalArgumentException ("Entry body is null");

        if (parentIDs == null)
            throw new IllegalArgumentException ("Parent IDs is null");

        return new MyEntry (entryID, entryBody, parentIDs);
    }

    /**
     * Format given {@link EntryID} object as human-readable string.
     *
     * @param entryID {@link EntryID} object
     * @return human-readable representation of given {@link EntryID} object
     */
    public static String formatEntryID (EntryID entryID)
    {
        if (entryID == null)
            throw new IllegalArgumentException ("Entry ID is null");

        return formatBytes (entryID.getBytes ());
    }

    /**
     * Format given {@link EntryBody} object as human-readable string.
     *
     * @param entryBody {@link EntryBody} object
     * @return human-readable representation of given {@link EntryBody} object
     */
    public static String formatEntryBody (EntryBody entryBody)
    {
        if (entryBody == null)
            throw new IllegalArgumentException ("Entry body is null");

        return formatBytes (entryBody.getBytes ());
    }

    /**
     * Format given {@link Entry} object as human-readable string.
     *
     * @param entry {@link Entry} object
     * @return human-readable representation of given {@link Entry} object
     */
    public static String formatEntry (Entry entry)
    {
        if (entry == null)
            throw new IllegalArgumentException ("Entry is null");

        StringBuilder result = new StringBuilder ();

        result.append ("Entry {id: ");
        result.append (formatEntryID (entry.getID ()));
        result.append (", body: ");
        result.append (formatEntryBody (entry.getBody ()));
        result.append (", parentIDs: [");

        boolean first = true;
        for (EntryID parentID: entry.getParentIDs ())
        {
            if (!first) result.append (", ");
            else first = false;
            result.append (formatEntryID (parentID));
        }
        result.append ("]}");

        return result.toString ();
    }

    private static String formatBytes (byte [] bytes)
    {
        int count = bytes.length;
        StringBuilder result = new StringBuilder (count * 2);
        for (int i = 0; i < count; i++)
            for (int j = 4; j >= 0; j -= 4)
                result.append (
                    "0123456789ABCDEF".charAt ((bytes [i] >>> j) & 0x0F));

        return result.toString ();
    }

    private static class MyEntryID extends AbstractEntryID
    {
        private final byte [] bytes;

        public MyEntryID (byte [] bytes)
        {
            if (bytes == null)
                throw new IllegalArgumentException ("Bytes is null");

            this.bytes = bytes.clone ();
        }

        @Override
        public byte [] getBytes ()
        {
            return bytes.clone ();
        }
    }

    private static class MyEntryBody extends AbstractEntryBody
    {
        private final byte [] bytes;

        public MyEntryBody (byte [] bytes)
        {
            if (bytes == null)
                throw new IllegalArgumentException ("Bytes is null");

            this.bytes = bytes.clone ();
        }

        @Override
        public byte [] getBytes ()
        {
            return bytes.clone ();
        }
    }

    private static class MyEntry extends AbstractEntry
    {
        private final EntryID id;
        private final EntryBody body;
        private final EntryID [] parentIDs;

        public MyEntry (
            EntryID id, EntryBody body, EntryID [] parentIDs)
        {
            if (id == null)
                throw new IllegalArgumentException ("ID is null");

            if (body == null)
                throw new IllegalArgumentException ("Body is null");

            if (parentIDs == null)
                throw new IllegalArgumentException ("Parent IDs is null");

            this.id = id;
            this.body = body;
            this.parentIDs = parentIDs.clone ();
        }

        @Override
        public EntryID getID ()
        {
            return id;
        }

        @Override
        public EntryBody getBody ()
        {
            return body;
        }

        @Override
        public EntryID [] getParentIDs ()
        {
            return parentIDs.clone ();
        }
    }
}
